/* 
 * File:   server_Grafico.cpp
 * Author: nahuel
 * 
 * Created on June 12, 2013, 7:24 PM
 */

#include "server_Grafico.h"
#include "common_FileUtils.h"
#include "common_ServerConfFileManager.h"
#include <glibmm-2.4/glibmm/main.h>
#include <iomanip>
#include <sstream>

// Porcentaje del ancho o alto total segun corresponda
#define     PADDING_LEFT         0.07
#define     PADDING_TOP          0.07
#define     PADDING_BOTTOM       0.07
#define     PADDING_RIGHT        0.07

// Porcentaje a agregar del maximo valor en Y a mostrar 
#define     PADDING_GRAPH_TOP      0.1
#define     PADDING_GRAPH_RIGHT    0.1

#define     PADDING_X_LABELS_BOTTOM   5
#define     PADDING_Y_LABELS_LEFT     0

#define     Y_AXIS_DIVISIONS     10
#define     X_AXIS_DIVISIONS     10

#define     DIVISION_SIZE        3  // Pixeles

#define     MAX_DATA_POINTS      100

#define     FONT_SIZE            8.0


Grafico::Grafico(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade)
: Gtk::DrawingArea(cobject) {
    // Refresh rate: 30 segundos
    Glib::signal_timeout().connect( sigc::mem_fun(*this, &Grafico::on_timeout), 30 * 1000);
    signal_draw().connect(sigc::mem_fun(*this, &Grafico::on_draw), false);
    
    // Root AU del servidor
    ServerConfFileManager confFile("../Servidor/config.xml");
    _pathAuServidor = confFile.GetPathAU();
    
    // Usado como referencia del origen de coordenadas    
    time(&_startTime);
    
    // Leemos el primer DataPoint
    ReadNewDataPoint();
}

bool Grafico::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
    cr->save();
    cr->set_source_rgb(0.699, 0.699, 0.699);
    cr->paint();
    cr->restore();
    
    int ancho = get_allocated_width();
    int alto = get_allocated_height();    
    
    // Y axis
    double yAxisBeg = alto * PADDING_TOP;
    double yAxisEnd = alto - (alto * PADDING_BOTTOM);
    double yAxisXCoord = ancho * PADDING_LEFT;
    
    cr->save();
    cr->set_source_rgb(0, 0, 0);
    cr->move_to(yAxisXCoord, yAxisBeg);
    cr->line_to(yAxisXCoord, yAxisEnd);
    cr->stroke();    
    
    // X axis
    double xAxisBeg = ancho * PADDING_LEFT;
    double xAxisEnd = ancho - (ancho * PADDING_RIGHT);
    double xAxisYCoord = alto - alto * PADDING_BOTTOM;
    
    cr->move_to(xAxisBeg, xAxisYCoord);
    cr->line_to(xAxisEnd, xAxisYCoord);
    cr->stroke();
    cr->restore();
    
    // Calculamos los maximos y minimos de los datos para las escalas
    list<DataPoint*>::iterator it;
    
    double minX = _listDatos.front()->x;
    double maxX = _listDatos.front()->x;
    double  minY = _listDatos.front()->y;
    double  maxY = _listDatos.front()->y;
    
    for (it = _listDatos.begin(); it != _listDatos.end(); it++) {
        DataPoint* current = *it;
        
        if (current->x < minX) minX = current->x;
        if (current->x > maxX) maxX = current->x;
        if (current->y < minY) minY = current->y;
        if (current->y > maxY) maxY = current->y;
    }
    
    // Si no se puede armar una escala, usamos una predefinida    
    if (minX == maxX) {
        if (maxX == 0) {
            maxX = 60;      // 1 min
        } else {
            minX = 0;
        }
    }
    
    if (minY == maxY) {
        if (maxY == 0) {
            maxY = 1024;    // 1 KB
        } else {
            minY = 0;
            
        }
    }
    
    // Ajustamos los maximos para que no termine sobre el borde del area a graficar
    maxX *= (1 + PADDING_GRAPH_RIGHT);
    maxY *= (1 + PADDING_GRAPH_TOP);    
    
    // Y axis divisions
    // Se divide por Y_AXIS_DIVISION + 1 para no terminar en el borde del eje
    double largoDivisionY = (alto - alto * PADDING_BOTTOM - alto * PADDING_TOP) / (double)(Y_AXIS_DIVISIONS + 1);    
    
    // Agregamos el label del origen del eje Y
    std::string labelY0 = GetYAxisLabel(0, maxY, minY);    
    WriteYAxisLabel(cr, labelY0, PADDING_Y_LABELS_LEFT, alto - (alto * PADDING_BOTTOM));
    
    for (unsigned int i = 1; i <= Y_AXIS_DIVISIONS; i++) {
        int divYCoord = alto - alto * PADDING_BOTTOM - i * largoDivisionY;
        
        cr->move_to(yAxisXCoord, divYCoord);
        cr->line_to(yAxisXCoord + DIVISION_SIZE, divYCoord);        
        cr->line_to(yAxisXCoord - DIVISION_SIZE, divYCoord);
        cr->stroke();
        
        std::string currentLabel = GetYAxisLabel(i, maxY, minY);        
        WriteYAxisLabel(cr, currentLabel, PADDING_Y_LABELS_LEFT, divYCoord);
    }    
    
    // X axis divisions
    // Se divide por X_AXIS_DIVISION + 1 para no terminar en el borde del eje
    double largoDivisionX = (ancho - ancho * PADDING_LEFT - ancho * PADDING_RIGHT) / (double)(X_AXIS_DIVISIONS + 1);    
    
    // Agregamos el label del origen del eje X
    std::string labelX0 = GetXAxisLabel(0, maxX, minX);    
    WriteXAxisLabel(cr, labelX0, ancho * PADDING_LEFT, alto - PADDING_X_LABELS_BOTTOM);
    
    for (unsigned int i = 1; i <= X_AXIS_DIVISIONS; i++) {
        int divXCoord = ancho * PADDING_LEFT + i * largoDivisionX;
        cr->move_to(divXCoord, xAxisYCoord);
        cr->line_to(divXCoord, xAxisYCoord + DIVISION_SIZE);
        cr->line_to(divXCoord, xAxisYCoord - DIVISION_SIZE);
        cr->stroke();
                
        std::string currentLabel = GetXAxisLabel(i, maxX, minX);        
        WriteXAxisLabel(cr, currentLabel, divXCoord, alto - PADDING_X_LABELS_BOTTOM);
    }

    // Data points
    it = _listDatos.begin();
    DataPoint* firstDataPoint = *it;
    
    double firstDataPointX = TransformX(firstDataPoint->x, xAxisBeg, xAxisEnd - largoDivisionX, minX, maxX);
    double firstDataPointY = TransformY(firstDataPoint->y, yAxisEnd, yAxisBeg + largoDivisionY, minY, maxY);
        
    cr->set_source_rgb(1, 0, 0);
    cr->arc(firstDataPointX, firstDataPointY, 1.5, 0, 2 * M_PI);
    cr->fill();
    cr->move_to(firstDataPointX, firstDataPointY);
    
    for (it++; it != _listDatos.end(); it++) {        
        DataPoint* current = *it;
        
        double x = TransformX(current->x, xAxisBeg, xAxisEnd - largoDivisionX, minX, maxX);
        double y = TransformY(current->y, yAxisEnd, yAxisBeg + largoDivisionY, minY, maxY);
        
        cr->line_to(x, y);        
        cr->stroke();
        cr->move_to(x, y);
    }   
}

bool Grafico::on_timeout() {   
    ReadNewDataPoint();
    
    // Fuerza el redibujado
    Glib::RefPtr<Gdk::Window> win = get_window();
    if (win) {
        Gdk::Rectangle r(0, 0, get_allocation().get_width(),
                get_allocation().get_height());
        win->invalidate_rect(r, false);
    }
    
    return true;
}

void Grafico::ReadNewDataPoint() {
    time_t currentTime;
    time(&currentTime);
            
    TP::Archivos::FileUtils fileUtils;
    double currentSize = fileUtils.GetPathSize(_pathAuServidor);
    
    DataPoint* dataPoint = new DataPoint();
    dataPoint->x = difftime(currentTime, _startTime);
    dataPoint->y = currentSize;
    
    _listDatos.push_back(dataPoint);
    
    while (_listDatos.size() > MAX_DATA_POINTS) _listDatos.pop_front();
}

std::string Grafico::YAxisFormatter(double y) {
    std::stringstream ss;
    
    if ( y >= 0 && y <= 1024) {
        ss << std::fixed << std::setprecision(0) << y;
        std::string bytes = ss.str();        
        
        return bytes + std::string("B");
    } else if (y <= (1024 * 1024)) {
        double sizeAsKb = y / 1024;
        
        ss << std::fixed << std::setprecision(0) << sizeAsKb;
        std::string kbs = ss.str();
        
        return kbs + std::string("KB");
    } else if (y <= (1024 * 1024 * 1024)) {
        double sizeAsMbs = y / (double)(1024 * 1024);
        
        ss << std::fixed << std::setprecision(1) << sizeAsMbs;
        std::string mbs = ss.str();
        
        return mbs + std::string("MB");
    } else {
        double sizeAsGbs = y / (double)(1024 * 1024 * 1024);
        
        ss << std::fixed << std::setprecision(1) << sizeAsGbs;
        std::string gbs = ss.str();
        
        return gbs + std::string("GB");
    }
}

std::string Grafico::XAxisFormatter(double x) {
    std::stringstream ss;
    
    if ( x >= 0 && x <= 60) {
        ss << std::fixed << std::setprecision(0) << x;
        std::string seconds = ss.str();        
        
        return seconds + std::string("\"");
    } else if (x <= 3600) {
        double timeAsMinutes = x / (double)60;
                
        ss << std::fixed << std::setprecision(1) << timeAsMinutes;
        std::string minutes = ss.str();
        
        return minutes + std::string("'");
    } else if (x > 3600) {
        double timeAsHours = x / (double)(60 * 60);
                
        ss << std::fixed << std::setprecision(1) << timeAsHours;
        std::string hours = ss.str();
        
        return hours + std::string(" horas");
    } else {
        return std::string("<error>");
    }    
}

std::string Grafico::GetYAxisLabel(unsigned int i, double maxY, double minY) {
    double value = i * (maxY - minY) / (double)Y_AXIS_DIVISIONS + minY;
    return YAxisFormatter(value);
}

std::string Grafico::GetXAxisLabel(unsigned int i, double maxX, double minX) {
    double value = i * (maxX - minX) / (double)X_AXIS_DIVISIONS + minX;
    return XAxisFormatter(value);
}

void Grafico::WriteXAxisLabel(const Cairo::RefPtr<Cairo::Context>& cr, const std::string& label, double x, double y) {
    cr->save();
    
    cr->set_font_size(FONT_SIZE);
    Cairo::TextExtents extents;
    cr->get_text_extents(label, extents);
    cr->move_to(x - (extents.width/2), y);
	cr->show_text(label);
    
    cr->restore();
}

void Grafico::WriteYAxisLabel(const Cairo::RefPtr<Cairo::Context>& cr, const std::string& label, double x, double y) {
    cr->save();
    
    cr->set_font_size(FONT_SIZE);
    Cairo::TextExtents extents;
    cr->get_text_extents(label, extents);
    cr->move_to(x, y + (extents.height/2));
    cr->show_text(label);

    cr->restore();
}

double Grafico::TransformX(double transformThis, double xAxisBeg, double xAxisEnd, double minX, double maxX) {
    return (xAxisEnd - xAxisBeg) / (maxX - minX) * (transformThis - minX) + xAxisBeg;
}

double Grafico::TransformY(double transformThis, double yAxisBeg, double yAxisEnd, double minY, double maxY) {
    return (yAxisEnd - yAxisBeg) / (maxY - minY) * (transformThis - minY) + yAxisBeg;
}

Grafico::~Grafico() {
    list<DataPoint*>::iterator it;
    
    for (it = _listDatos.begin(); it != _listDatos.end(); it++) {
        delete *it;
    }
}

